﻿using System;
using System.Globalization;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Input;
using System.Windows.Interactivity;

using MugenMvvmToolkit.Interfaces;

using MugenMvvmToolkit.Models;

namespace MugenMvvmToolkit.Behaviors
{
    /// <summary>
    /// This <see cref="EventToCommand" /> can be
    /// used to bind any event on any FrameworkElement to an <see cref="ICommand" />.
    /// Typically, this element is used in XAML to connect the attached element
    /// to a command located in a ViewModel. This trigger can only be attached
    /// to a FrameworkElement or a class deriving from FrameworkElement.
    /// </summary>
    public sealed class EventToCommand : TriggerAction<FrameworkElement>
    {
        #region Fields

        private object _cmdParam;
        private ICommand _currentCommand;
        private bool _isChangeEnabled;
        private IValueConverter _valueConverter;
        private ICondition _eventCondition;

        #endregion

        #region Constructors

        /// <summary>
        /// Initializes a new instance of the <see cref="EventToCommand"/> class.
        /// </summary>
        public EventToCommand()
        {
        }

        #endregion

        #region Dependency properties

        /// <summary>
        /// Identifies the <see cref="EventParameterConverter"/> dependency property.
        /// </summary>
        public static readonly DependencyProperty EventParameterConverterProperty =
            DependencyProperty.Register("EventParameterConverter", typeof(IValueConverter), typeof(EventToCommand),
                                        new PropertyMetadata(default(IValueConverter), (o, args) =>
                                                                                           {
                                                                                               var eventManager =
                                                                                                   o as EventToCommand;
                                                                                               if (eventManager == null)
                                                                                                   return;
                                                                                               eventManager._valueConverter =
                                                                                                   (IValueConverter)args.NewValue;
                                                                                           }));

        /// <summary>
        /// Identifies the <see cref="IsChangeEnabledState" /> dependency property.
        /// </summary>
        public static readonly DependencyProperty IsChangeEnabledStateProperty =
            DependencyProperty.Register("IsChangeEnabledState", typeof(bool), typeof(EventToCommand),
                                        new PropertyMetadata(false, (o, args) =>
                                                                        {
                                                                            var eventManager = o as EventToCommand;
                                                                            if (eventManager == null) return;
                                                                            eventManager._isChangeEnabled =
                                                                                (bool)args.NewValue;
                                                                        }));

        /// <summary>
        /// Identifies the <see cref="CommandParameter" /> dependency property.
        /// </summary>
        public static readonly DependencyProperty CommandParameterProperty =
            DependencyProperty.Register("CommandParameter", typeof(object), typeof(EventToCommand),
                                        new PropertyMetadata(default(object), (o, args) =>
                                                                                  {
                                                                                      var eventManager =
                                                                                          o as EventToCommand;
                                                                                      if (eventManager == null) return;
                                                                                      eventManager._cmdParam =
                                                                                          args.NewValue;
                                                                                  }));

        /// <summary>
        /// Identifies the <see cref="Command" /> dependency property.
        /// </summary>
        public static readonly DependencyProperty CommandProperty =
            DependencyProperty.Register("Command", typeof(ICommand), typeof(EventToCommand),
                                        new PropertyMetadata(default(ICommand), (o, args) =>
                                                                                    {
                                                                                        var eventManager =
                                                                                            o as EventToCommand;
                                                                                        if (eventManager == null)
                                                                                            return;
                                                                                        eventManager.SetCommand(
                                                                                            args.NewValue as ICommand);
                                                                                    }));

        /// <summary>
        /// Identifies the <see cref="EventCondition" /> dependency property.
        /// </summary>
        public static readonly DependencyProperty EventConditionProperty =
            DependencyProperty.Register("EventCondition", typeof(ICondition), typeof(EventToCommand),
                                        new PropertyMetadata(default(ICondition),
                                                             (o, args) =>
                                                             {
                                                                 var eventManager = o as EventToCommand;
                                                                 if (eventManager == null) return;
                                                                 eventManager._eventCondition = (ICondition)args.NewValue;
                                                             }));

        /// <summary>
        /// Gets or sets a value indicating whether the attached element must be
        /// disabled when the <see cref="Command" /> property's CanExecuteChanged
        /// event fires.
        /// </summary>
        public bool IsChangeEnabledState
        {
            get { return (bool)GetValue(IsChangeEnabledStateProperty); }
            set { SetValue(IsChangeEnabledStateProperty, value); }
        }

        /// <summary>
        /// Gets or sets an object that will be passed to the <see cref="Command" /> attached to this trigger.
        /// </summary>
        public object CommandParameter
        {
            get { return GetValue(CommandParameterProperty); }
            set { SetValue(CommandParameterProperty, value); }
        }

        /// <summary>
        /// Gets or sets the <see cref="ICommand"/> that this trigger is bound to.
        /// </summary>
        public ICommand Command
        {
            get { return (ICommand)GetValue(CommandProperty); }
            set { SetValue(CommandProperty, value); }
        }

        /// <summary>
        /// Gets or sets <see cref="IValueConverter"/> for convert event parameter.
        /// </summary>
        public IValueConverter EventParameterConverter
        {
            get { return (IValueConverter)GetValue(EventParameterConverterProperty); }
            set { SetValue(EventParameterConverterProperty, value); }
        }

        /// <summary>
        /// Gets or sets the specified event condition.
        /// </summary>
        public ICondition EventCondition
        {
            get { return (ICondition)GetValue(EventConditionProperty); }
            set { SetValue(EventConditionProperty, value); }
        }

        #endregion

        #region Overrides of TriggerAction

        /// <summary>
        /// Invokes the action.
        /// </summary>
        /// <param name="parameter">The parameter to the action. If the action does not require a parameter, the parameter may be set to a null reference.</param>
        protected override void Invoke(object parameter)
        {
            CommandParameter param;
            if (!CanExecute(parameter, out param)) return;
            _currentCommand.Execute(param);
        }

        #endregion

        #region Properties

        private bool IsEnabledElement
        {
            get
            {
                if (AssociatedObject == null)
                    return false;
#if SILVERLIGHT
                var control = AssociatedObject as Control;
                if (control == null) return true;
                return control.IsEnabled;
#else
                return AssociatedObject.IsEnabled;
#endif
            }

            set
            {
                if (AssociatedObject == null) return;
#if SILVERLIGHT
                var control = AssociatedObject as Control;
                if (control == null) return;
                control.IsEnabled = value;
#else
                AssociatedObject.IsEnabled = value;
#endif
            }
        }

        #endregion

        #region Methods

        /// <summary>
        /// Invoke this trigger programatically without any event parameter.
        /// </summary>
        public void Execute()
        {
            Invoke(null);
        }

        /// <summary>
        /// Invoke this trigger programatically with any event parameter.
        /// </summary>
        public void Execute(object param)
        {
            Invoke(param);
        }

        private bool CanExecute(object parameter, out CommandParameter commandParameter)
        {
            commandParameter = null;
            if (AssociatedObject == null || !IsEnabledElement || _currentCommand == null)
                return false;
            commandParameter = new CommandParameter(CommandParameter, GetEventParameter(parameter));
            if (_eventCondition != null && !_eventCondition.CanExecute(commandParameter))
                return false;
            return _currentCommand.CanExecute(commandParameter);
        }

        private void SetCommand(ICommand newCommand)
        {
            if (_currentCommand != null)
            {
                _currentCommand.CanExecuteChanged -= CommandOnCanExecuteChanged;
                _currentCommand = null;
            }
            if (newCommand == null) return;
            _currentCommand = newCommand;
            _currentCommand.CanExecuteChanged += CommandOnCanExecuteChanged;
            UpdateEnabledState();
        }

        private void UpdateEnabledState()
        {
            FrameworkElement frameworkElement = AssociatedObject;
            if (frameworkElement == null || _currentCommand == null || !_isChangeEnabled) return;
            IsEnabledElement = _currentCommand.CanExecute(new CommandParameter(_cmdParam, null));
        }

        private object GetEventParameter(object parameter)
        {
            return _valueConverter == null
                       ? parameter
                       : _valueConverter.Convert(parameter, null, null, CultureInfo.CurrentCulture);
        }

        #endregion

        #region Events

        private void CommandOnCanExecuteChanged(object sender, EventArgs eventArgs)
        {
            UpdateEnabledState();
        }

        #endregion
    }
}